import json
from datetime import date
from decimal import Decimal
from django.db.models import Sum, F, Case, When, IntegerField, DecimalField, Value, QuerySet
from django.db.models.functions import Coalesce
from django.http import HttpResponse
from django.views.decorators.http import require_http_methods
from administrators.views.token_check import verify_token
from inventory.export_table.angle_steel import make_true_angle_steel_table, make_weight_angle_steel_table, auto_store_table
from inventory.export_table.other import make_new_table, export_Type1
from inventory.export_table.scrap_steel import make_fp_table
from inventory.export_table.strip_steel import make_now_strip_steel_table, strip_steel_auto_store_table
from inventory.jingzhi_api.api_list import req_all_checklist
from inventory.models import Material, Record, MaterialGroup
from inventory.views.func.datatype import special_all_angle_steel_list, whole_piece_weight_d_value, angle_weight_table, \
    strip_steel_sale_table, angle_steel_manual_table
from utils.enums import SCE
from utils.exceptions import BusinessException
from utils.result import R
from utils.tools import ParamCheck
from urllib import parse


# 从景智系统中取得销售记录
@verify_token()
@require_http_methods(['POST'])
def get_data_from_jingzhi(request, user_id: int):
    try:
        data = json.loads(request.body)
        car_type_id = ParamCheck.car_type(data.get('car_type_name'))
        date_range: tuple[str, str] = ParamCheck.date_range(data['date_range'], date_type='str')
        state = ParamCheck.checklist_state(data['state'])
        result, total = req_all_checklist(car_type_id=car_type_id, time_range=date_range, state=state)
        return R.success('从景智系统请求数据成功').data({'result': result, 'total': total})
    except Exception:
        return BusinessException(SCE.unexpected_ERR)


def history_data(date_point: date, material_id: int, set_piece: int) -> int:
    """
    如果需要某物料在某天结束时的支数，那么需要用最新支数减去这之间的差值
    该函数用于计算该差值，之后直接减掉该差值即可
    """
    result1: dict[str, int] = (Record.objects
    .filter(actual_date__gt=date_point, material_id=material_id)
    .aggregate(
        total_inventory=Sum(Case(
            # 入库记录加上数值
            When(trend=False, then=(F('whole_piece') * set_piece) + F('scatt_piece')),
            # 出库记录减去数值
            When(trend=True, then=-((F('whole_piece') * set_piece) + F('scatt_piece'))),
            default=0,
            output_field=IntegerField()
        )
        )
    ))
    if result1['total_inventory'] is None:
        result1['total_inventory'] = 0
    return result1['total_inventory']


def angle_steel_sale_use_data_list(manganese: bool, export_date: date) -> special_all_angle_steel_list:
    """
    返回数据格式如下
    {
        40*3*6: {
            set_piece: 187,
            material: {4036A: {whole_piece: 1, scatt_piece: 0, id: 1369}
            }
        }
    }
    """
    if manganese:  # 如果是锰角钢
        field2 = '345'
    else:
        field2 = '235'

    data2: special_all_angle_steel_list = {}
    cmy2 = list(Material.objects
                .values('code', 'model', 'scatt_piece', 'whole_piece', 'set_piece', 'latest_date', 'id')
                .filter(group__name='角钢', steel_material__name=field2))
    for i in cmy2:
        # 如果没有这个规格的角钢
        if i['model'] not in data2.keys():
            data2[i['model']] = {'set_piece': i['set_piece'], 'material': {}}
        # 保存该物料
        if export_date < i['latest_date']:
            num = history_data(export_date, i['id'], i['set_piece'])
            whole_piece, scatt_piece = divmod(i['whole_piece'] * i['set_piece'] + i['scatt_piece'] - num,
                                              i['set_piece'])
        else:
            whole_piece: int = i['whole_piece']
            scatt_piece: int = i['scatt_piece']
        # 如果是零则改为空字符串
        data2[i['model']]['material'][i['code']] = {
            'id': i['id'],
            'whole_piece': whole_piece,
            'scatt_piece': scatt_piece
        }
    return data2


# 打印角钢销售用库存表
@verify_token()
@require_http_methods(['POST'])
def download_angle_steel_table_true_store(request, user_id: int):
    data = json.loads(request.body)
    export_date: date = ParamCheck.date(data.get('export_date'))

    data1 = angle_steel_sale_use_data_list(False, export_date)
    data2 = angle_steel_sale_use_data_list(True, export_date)

    xl = make_true_angle_steel_table(export_date, data1, data2)
    response = HttpResponse()
    response['content-type'] = 'application/vnd.ms-excel'
    response['Content-Disposition'] = 'attachment;filename=' + parse.quote(f'{export_date}_角钢库存表.xls')
    response['Access-Control-Expose-Headers'] = 'Content-Disposition'
    xl.save(response)
    return response


def weight_history_data(date_point: date, material_id: int) -> whole_piece_weight_d_value:
    """
    返回某物料指定日期的件数和实际重量和最新日期之间的差值，不考虑支数
    """
    result1: dict = (Record.objects
    .filter(actual_date__gt=date_point, material_id=material_id)
    .aggregate(
        piece=Coalesce(Sum(Case(
            When(trend=False, then=(F('whole_piece'))),
            When(trend=True, then=-(F('whole_piece'))),
            default=0,
            output_field=IntegerField()
        )),
            Value(0),
            output_field=IntegerField()
        ),
        weight=Coalesce(Sum(Case(
            When(trend=False, then=(F('weight'))),
            When(trend=True, then=-(F('weight'))),
            default=0,
            output_field=DecimalField(max_digits=11, decimal_places=3)
        )),
            Value(0),
            output_field=DecimalField(max_digits=11, decimal_places=3)
        ),
    )
    )
    return {'piece': result1['piece'], 'weight': result1['weight']}


def angle_steel_weight_data_list(export_date: date, manganese: bool) -> angle_weight_table:
    if manganese:  # 如果是锰角钢
        field2 = '345'
    else:
        field2 = '235'
    data2: angle_weight_table = {}
    cmy2 = (Material.objects
            .values('theory_scatt_weight', 'whole_piece', 'weight', 'theory_weight', 'id', 'latest_date',
                    'set_piece', 'model')
            .filter(group__name='角钢', steel_material__name=field2))
    for i in cmy2:
        # 如果这个规格的最近出入日期小于导出日期，且正品和次品库都为0，那就直接跳过
        if i['latest_date'] < export_date and i['whole_piece'] == 0:
            continue
        # 如果没有这个规格的角钢，添加这个规格
        if i['model'] not in data2.keys():
            data2[i['model']] = {'whole_piece': 0,
                                 'weight': Decimal('0'),
                                 'theory_weight': Decimal('0')}
        # 该物料的最新数据
        t_whole_piece = i['whole_piece']
        t_weight = i['weight']
        t_theory_weight = i['theory_weight']
        # 如果正品库和次品库其中一个不是最新，就都倒一下。
        if export_date < i['latest_date']:
            num = weight_history_data(export_date, i['id'])
            t_whole_piece -= num['piece']
            t_weight -= num['weight']
            # 计算理论重量
            t_theory_weight = i['theory_scatt_weight'] * i['set_piece'] * t_whole_piece
        data2[i['model']]['whole_piece'] += t_whole_piece
        data2[i['model']]['weight'] += t_weight
        data2[i['model']]['theory_weight'] += t_theory_weight
    return data2


# 打印角钢企管表
@verify_token()
@require_http_methods(['POST'])
def download_angle_steel_table_weight(request, user_id: int):
    data = json.loads(request.body)
    export_date: date = ParamCheck.date(data.get('export_date'))
    p = angle_steel_weight_data_list(export_date, False)
    m = angle_steel_weight_data_list(export_date, True)
    xl = make_weight_angle_steel_table(export_date, p, m)
    response = HttpResponse()
    response['content-type'] = 'application/vnd.ms-excel'
    response['Content-Disposition'] = 'attachment;filename=' + parse.quote(f'{export_date}_角钢企管表.xls')
    response['Access-Control-Expose-Headers'] = 'Content-Disposition'
    xl.save(response)
    return response


# 带钢表内部函数
def history_strip_steel_table(export_date: date, group_name: str, blank: bool) -> list[strip_steel_sale_table]:
    """
    返回带钢用销售库存表所需数据
    :param export_date: 导出日期，意即需要该日期结束时的库存状态
    :param group_name: 物料分组
    :param blank: 如果该物料无出入库信息，是否还需要 true为需要
    """
    material_group_obj = MaterialGroup.objects.get(name=group_name)
    data = list(Material.objects
                .values('model', 'whole_piece', 'name', 'weight', 'latest_date', 'code', 'id')
                .filter(group=material_group_obj)
                .order_by('model', 'name'))
    true_data: list[strip_steel_sale_table] = []
    # 修正需要倒扣的数据
    for i in data:
        true_temp1: strip_steel_sale_table = {
            'id': i['id'],
            'code': i['code'],
            'model': i['model'],
            'name': i['name'],
            'whole_piece': i['whole_piece'],
            'weight': i['weight'],
        }
        # 进行倒扣
        if export_date < i['latest_date']:
            num = weight_history_data(export_date, i['id'])
            true_temp1['whole_piece'] -= num['piece']
            true_temp1['weight'] -= num['weight']
        # 需要倒扣的倒扣完之后，看是否需要
        if not blank:
            if true_temp1['whole_piece'] != 0:
                true_data.append(true_temp1)
        else:
            true_data.append(true_temp1)
    return true_data


# 打印销售用带钢库存表
@verify_token()
@require_http_methods(['POST'])
def download_history_strip_steel_table(request, user_id: int):
    data = json.loads(request.body)
    export_date = ParamCheck.date(data.get('export_date'))
    true_data = history_strip_steel_table(export_date, '带钢', False)
    # 带钢制表
    xl = make_now_strip_steel_table(true_data, export_date)
    response = HttpResponse()
    response['content-type'] = 'application/vnd.ms-excel'
    response['Content-Disposition'] = 'attachment;filename=' + parse.quote(f'{export_date}_带钢库存表.xls')
    response['Access-Control-Expose-Headers'] = 'Content-Disposition'
    xl.save(response)
    return response


# 打印废品销售表
@verify_token()
@require_http_methods(['POST'])
def download_scrap_steel(request, user_id: int):
    data = json.loads(request.body)
    date_range: tuple[str, str] = ParamCheck.date_range(data.get('export_date'), date_type='str')
    result, total = req_all_checklist('b703f6bd11b24e0c89f61402b593fe28', date_range, '1')
    # 按规格分类
    result_2 = {}
    for i in result:
        if i['name'] not in result_2:
            result_2[i['name']] = Decimal('0')
        result_2[i['name']] += i['weight']
    result2 = []
    for key, value in result_2.items():
        result2.append({'name': key, 'weight': value})
    # 按客户分类
    result_3 = {}
    for i in result:
        if i['customer_name'] not in result_3:  # 如果客户名称不存在
            result_3[i['customer_name']] = {}
        if i['name'] not in result_3[i['customer_name']]:  # 如果该客户下没有这种物料
            result_3[i['customer_name']][i['name']] = Decimal(0)
        result_3[i['customer_name']][i['name']] += i['weight']
    xl = make_fp_table(result, result2, result_3)
    response = HttpResponse()
    response['content-type'] = 'application/vnd.ms-excel'
    response['Content-Disposition'] = 'attachment;filename=' + parse.quote(f'{date_range[0]}_{date_range[1]}_废品.xls')
    response['Access-Control-Expose-Headers'] = 'Content-Disposition'
    xl.save(response)
    return response


# 打印某个日期的角钢的出库和入库，按照旧有自动库存表的格式，没有支数
@verify_token()
@require_http_methods(['POST'])
def angle_steel_auto_store_table(request, user_id: int):
    data = json.loads(request.body)
    try:
        export_date = ParamCheck.date(data.get('export_date'))
        material_group = ParamCheck.int(data.get('material_group'))
        material_group_name = MaterialGroup.objects.get(id=material_group).name
        if material_group_name == '角钢':
            all_data: QuerySet = (Record.objects
                                  .filter(actual_date=export_date, material__group__name='角钢')
                                  .values('material__code', 'whole_piece', 'weight'))
            in_data: list[angle_steel_manual_table] = list(all_data.filter(trend=False))
            out_data = list(all_data.filter(trend=True))
            xl = auto_store_table(in_data, out_data)
            response = HttpResponse()
            response['content-type'] = 'application/vnd.ms-excel'
            response['Content-Disposition'] = 'attachment;filename=' + parse.quote(
                f'{export_date}_角钢自动库存表数据.xls')
            response['Access-Control-Expose-Headers'] = 'Content-Disposition'
            xl.save(response)
            return response
        elif material_group_name == '带钢':
            all_data: QuerySet = (Record.objects
                                  .filter(actual_date=export_date, material__group__name='带钢')
                                  .values('material__code', 'whole_piece', 'weight'))
            in_data: list[angle_steel_manual_table] = list(all_data.filter(trend=False))
            out_data: list[angle_steel_manual_table] = list(all_data.filter(trend=True))

            xl = strip_steel_auto_store_table(in_data, out_data)
            response = HttpResponse()
            response['content-type'] = 'application/vnd.ms-excel'
            response['Content-Disposition'] = 'attachment;filename=' + parse.quote(
                f'{export_date}_角钢自动库存表数据.xls')
            response['Access-Control-Expose-Headers'] = 'Content-Disposition'
            xl.save(response)
            return response
        else:
            raise BusinessException(SCE.ParamError)
    except Exception as e:
        raise BusinessException(SCE.no_enum, f'未知异常：{e}')


# 导出角钢或者带钢按照材质分类的每日重量出入表，用于月结
@verify_token()
@require_http_methods(['POST'])
def product_material_inout_table(request, user_id: int):
    data = json.loads(request.body)
    date_range: tuple[date, date] = ParamCheck.date_range(data.get('export_date'), date_type='date')
    material_group = ParamCheck.int(data.get('material_group'))
    material_group_obj = MaterialGroup.objects.get(id=material_group)

    result: QuerySet = Record.objects.filter(
        material__group=material_group_obj,
        actual_date__range=date_range
    ).values(
        'actual_date', 'material__steel_material__name'
    ).annotate(
        total_in=Coalesce(
            Sum(
                Case(
                    When(trend=False, then='weight'),
                    default=Value(0),
                    output_field=DecimalField(max_digits=11, decimal_places=3)
                )
            ),
            Value(0),
            output_field=DecimalField(max_digits=11, decimal_places=3)
        ),
        total_out=Coalesce(
            Sum(
                Case(
                    When(trend=True, then='weight'),
                    default=Value(0),
                    output_field=DecimalField(max_digits=11, decimal_places=3)
                )
            ),
            Value(0),
            output_field=DecimalField(max_digits=11, decimal_places=3)
        )
    ).exclude(
        total_in=0, total_out=0  # 排除当天无进出的情况
    ).order_by('actual_date')

    table_title = list(
        Record.objects.filter(
            material__group_id=material_group_obj,
            actual_date__range=date_range  # 日期范围
        )
        .values_list('material__steel_material__name', flat=True)
        .distinct()  # 去重
    )

    result: list[export_Type1] = list(result)
    # 制表
    xl = make_new_table(table_title, result, date_range)
    response = HttpResponse()
    response['content-type'] = 'application/vnd.ms-excel'
    response['Content-Disposition'] = 'attachment;filename=' + parse.quote('每日出入库表（材质分类）.xls')
    response['Access-Control-Expose-Headers'] = 'Content-Disposition'
    xl.save(response)
    return response
